#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stddef.h>
#include <dirent.h>
#include <pthread.h>
#include <limits.h>

// I write there main advises, and if you see !number!, it means, that i ask to show more attention on it 
//(because i do this mistakes, and have a lot of problems:  
// !1!: it's too difficult to me read it ( i am bad reader), but your code has to be well-readed for everybody 
// 2: maybe, you cut down amount of your data? it's seems to me, that some of them are equal.( if i didn't understand something, forget it! :) )  

//#include <heap1.h>

//correct actions with struct data? (does it exist during the whole program?)
//probably we need msync
//maybe sth was not alligned...
//TODO: again check aligning
extern errno;

static size_t page_size = 0;

int is_initialised = 0;

//TODO: remoove printf

typedef struct NODE {
    size_t id;
    //void* memloc;
    off_t off;
    size_t size;
    struct NODE * next;
} node_t;

typedef struct LIST {
    node_t * first;
    node_t * last;
} list_t;
//TODO: errors
//TODO:
int addnode(list_t * list, size_t val, size_t size, off_t off)//change it
{
    node_t * n;

    if ( ! ( n = malloc(sizeof(node_t)) ) )
        return -1;

    n->id = val;
    n->next = NULL;
    n->size = size;
    n->off = off;

    if ( list->first == NULL )
        list->first = n;
    else
        list->last->next = n;
    list->last = n;

    return 0;
}

void clearlist(list_t * list)
{
    while ( list->first != NULL ){
        list->last = list->first->next;
        free(list->first);
        list->first = list->last;
    }
}

node_t * findbyval(list_t * list, size_t val)
{
    node_t * ptr = list->first;

    while ( ptr != NULL && ptr->id != val )
        ptr = ptr->next;
    /*//if there is no node with such val
    if ((ptr == list->first) && (ptr->id != val))
        return NULL;*/
    return ptr;
}

node_t * findbysize(list_t * list, size_t size)
{
    node_t * ptr = list->first;

    while ( ptr != NULL && ptr->size < size )
        ptr = ptr->next;
    /*//if there is no node with such val
    if ((ptr == list->first) && (ptr->size != size))
        return NULL;*/
    return ptr;
}

int delnode(list_t * list, node_t * node)
{
    if ( list->first == node )
        list->first = list->first->next;
    else {
        node_t * ptr;
        for ( ptr = list->first; ptr->next != NULL && ptr->next != node; ptr = ptr->next )
            ;
        if ( ptr->next == NULL )
            return -1;
        else if ( ptr->next == list->last )
            list->last = ptr;
        ptr->next = node->next;
    }

    free(node);
    return 0;
}

size_t align_to_page_size(size_t size) {
    page_size = sysconf(_SC_PAGESIZE);
    return (((~(page_size-1))&size) + page_size);
}

struct mem_control_block
{
    int is_available;//if 1 than available
    size_t size;
    size_t id;
    void* memloc;
    off_t off;//for swap
    int is_mapped;//1 if it is mapped
};//it is before each chunk, consists of metadata about this particular chunk

//TODO: this struct is in h-file; delete it here

struct data
{
    void* start;//managed_memory_start
    void* last;//last_valid_address
    //char* swap_path;
    int has_initialised;
    int fd;
    off_t off;
    list_t list;//list of nodes in swap
    list_t swap_list;//list of free space (which was freed) in swap
    size_t mem;
    size_t swap;
    size_t id;
    int end;//if we've run out of valid ids=>1 else 0
};

struct data* data_t;

void poison_mem (void* ptr, size_t size)
{
    int i;
    char* symb = (char*)ptr;
    for (i = 0; i < size; i++) {
        symb[i] = 0x5a;
    }
}

void ma_init(size_t mem, size_t swap, const char* swap_path) // it is 'int' function
{
    if (is_initialised == 0)
        data_t = (struct data*) malloc (sizeof (struct data));
    page_size = sysconf(_SC_PAGESIZE);
    if (mem>swap) {
        errno=EBADR;
        perror("");
        exit (EXIT_FAILURE);
    }
    if (data_t->has_initialised == 1) {
        errno = EALREADY;
        perror("can't initialise");
        return;
    }
    void* buf;
    size_t size = align_to_page_size(mem); // Good idea! I must to think about similiar
    if (posix_memalign(&buf, page_size, size)!=0) {
        errno = ENOMEM;
        perror("can't allocate memory");
        exit (EXIT_FAILURE);
    }
    printf ("init\n");
    //poison_mem(data_t->start, size); //TODO!!!!!!!!!!!!!!!!!!!!!!
    int fd=open(swap_path, O_RDWR|O_CREAT,0666);
    if(fd == -1) {
        if (errno!=EACCES)
            errno=EFAULT;
        perror("");
        exit (EXIT_FAILURE);
    }
    //data_t->swap_path=swap_path;
    if (ftruncate(fd, (off_t)swap) != 0)
        perror("");
    is_initialised = 1;
    data_t->fd=fd;
    data_t->start = buf; // why do we need buf? we didn't change or use it
    data_t->last = data_t->start;
    data_t->has_initialised = 1;
    data_t->mem=size;
    data_t->off = 0;
    data_t->end = 0;
    data_t->swap = swap;
    data_t->id = 1;
    data_t->list.first = NULL;
    data_t->list.last = NULL;
    data_t->swap_list.first = NULL;
    data_t->swap_list.last = NULL;
}

void ma_deinit()// what about memconrolblock? don't find where it delete
{
    printf ("deinit_begins\n");
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return;
    }
    if (close(data_t->fd) == -1)
        perror ("can't close op1");
    free(data_t->start);
    free(data_t);
}


//TODO: ma_free must have type int!!!
int ma_free(size_t id)
{
    page_size = sysconf(_SC_PAGESIZE);
    node_t* node;
    int ret;
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return 0;
    }
    if (id >= data_t->id) {
        errno = EFAULT;
        perror ("was not allocated yet");
    }
    void *cloc;
    struct mem_control_block *loc_mcb;
    cloc = data_t->start;
    printf ("1 step of ma_free\n");
    while(cloc != data_t->last) {
        loc_mcb = (struct mem_control_block *)cloc;
        if(loc_mcb->id == id) {
            //poison_mem(loc_mcb->memloc, loc_mcb->size);//TODO
            if (loc_mcb->is_available == 0) {
                errno = EBUSY;
                perror("was not released");
                return 0;
            }
        }


        cloc = cloc + loc_mcb->size;
    }

    node = findbyval (&data_t->list, id);
    if (node == NULL) {
        errno = EFAULT;
        perror("no memory chunk with such id");//
        return 0;
    }
    //TODO: cicle for finding id in swap file and freeing it
    ret = addnode(&data_t->swap_list, node->id, node->size, node->off);
    delnode(&data_t->list, node);
    printf ("free\n");
    return 1;
}

unsigned ma_alloc(size_t sz) // unsigned?
{
    printf ("1 step of alloc %d\n", data_t->mem);
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return 0;
    }
    if (data_t->end == 1) {
        errno = EMLINK;
        perror("run out of valid ids");
        return 0;
    }
    if (data_t->mem < sz) {
        errno = E2BIG;
        perror("too big chunk");
        return 0;
    }
    size_t size = align_to_page_size(sz);
    void *cloc;//current_location
    struct mem_control_block *loc_mcb;//current_location_mcb
    void *memloc;//memory_location
    size = size + align_to_page_size(sizeof(struct mem_control_block));
    memloc = 0;
    cloc = data_t->start;
    while(cloc != data_t->last) {
        loc_mcb = (struct mem_control_block *)cloc;
        if(loc_mcb->is_available) { // this part of code is used very often. what about function?
            if(loc_mcb->size >= size) {
                loc_mcb->is_available = 0;
                memloc = cloc;
                break;
            }
        }
        cloc = cloc + loc_mcb->size;
    }
    if(! memloc) {		
        memloc = data_t->last;
        printf ("41 step of alloc\n");
        if (data_t->last + size>data_t->start+data_t->mem) {
            errno = ENOMEM;
            perror("not enough memory in heap");
            return 0;
        }
        data_t->last = data_t->last + size;
        loc_mcb = (struct mem_control_block*)memloc;
        loc_mcb->is_available = 0;
        loc_mcb->size = size;
        loc_mcb->is_mapped = 0;
        printf ("43 step of alloc\n");
    }
    memloc = memloc + align_to_page_size(sizeof(struct mem_control_block));
    loc_mcb->id = data_t->id;
    printf ("id = %d\n", loc_mcb->id);
    if (data_t->id != (size_t)-1)
        data_t->id++;
    else
        data_t->end = 1;

    return loc_mcb->id;
}

void* ma_get(size_t id) // nothing to add, but maybe more easiest way exist? too many if..else
{			// if i were you, i would determine it on other functions
    printf ("1 step of ma_get\n");
    page_size = sysconf(_SC_PAGESIZE);
    void* ptr;
    size_t size;
    node_t* node;
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return NULL;
    }
    void *cloc;
    void *memloc;
    node_t* node1;
    struct mem_control_block *loc_mcb;
    off_t addr;
    int ret;
    cloc = data_t->start;
    while(cloc != data_t->last) {
        loc_mcb = (struct mem_control_block *)cloc;
        addr = (((off_t)(loc_mcb->size) + page_size - 1) / page_size) * page_size;
        if ((loc_mcb->id == id) && (loc_mcb->is_available == 0)){
            printf ("get found sth in heap!\n");
            //if we can't mmap on the last unmapped position of file than we find unoccupied position
            if (loc_mcb->is_mapped == 1) {
                return loc_mcb->memloc;
            }
            if (data_t->off+loc_mcb->size>data_t->swap) {
                printf ("and we add node to the middle of the swap file");
                node1 = findbysize(&data_t->swap_list, addr);
                if (node1 == NULL) {
                    //errno = ...???
                    //perror("no space in file");//TODO
                    return NULL;
                }
                ptr = mmap(loc_mcb->memloc, addr,
                        PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                        data_t->fd, node1->off);
                if (ptr == MAP_FAILED) {
                    errno = EBADF;
                    perror("problems with mapping the file");
                }
                loc_mcb->is_mapped = 1;
                ret = addnode(&data_t->list, node1->id, node1->size, node1->off);
                delnode(&data_t->swap_list, node1);
                //addnode
            }
            else {
                printf ("and we add the node to the end of the swap file\n");
                //printf ("kaban\n");
                ptr = mmap(loc_mcb->memloc, addr,
                    PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                    data_t->fd, data_t->off);
                if (ptr == MAP_FAILED) {
                    errno = EBADF;
                    perror("problems with mapping the file");
                }
                loc_mcb->is_mapped = 1;
                ret = addnode(&data_t->list, id, addr, data_t->off);
                data_t->off = data_t->off + addr;
            }
            return ptr;
        }
        cloc = cloc + loc_mcb->size;
    }
    printf ("2 step of ma_get: no chunk in heap; id = %d\n", id);
    //if we reached this place, there is no chunk with such id in heap
    //but it may be in swap file
    node = findbyval (&data_t->list, id);
    if (node == NULL) {
        errno = EFAULT;
        perror("no memory chunk with such id");//step 2 is quiqer then 1. maybe replace them?
        return NULL;
    }

    size = node->size;
    cloc = data_t->start;

    while(cloc != data_t->last) {
        loc_mcb = (struct mem_control_block *)cloc;
        addr = (((off_t)(loc_mcb->size) + page_size - 1) / page_size) * page_size;
        if ((loc_mcb->is_available) && (loc_mcb->size >= size)) {
            loc_mcb->is_available = 0;
            memloc = cloc;
            ptr = mmap(loc_mcb->memloc, addr,
                    PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                    data_t->fd, node1->off);
            loc_mcb->is_mapped = 1;
            if (ptr == MAP_FAILED) {
                errno = EBADF;
                perror("problems with mapping the file");
            }
            return ptr;
        }
        cloc = cloc + loc_mcb->size;
    }
    //if we are here there is no place in heap
    errno = ENOMEM;
    perror("not enough memory in heap");
    return NULL;

}
void ma_release(size_t id)// it is 'int' function
{
    page_size = sysconf(_SC_PAGESIZE);
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return;
    }
    void *cloc;
    void* prevloc;
    void* nextloc;
    struct mem_control_block *loc_mcb;
    struct mem_control_block *prevloc_mcb;
    struct mem_control_block *nextloc_mcb;
    cloc = data_t->start;
    while(cloc != data_t->last) {
        loc_mcb = (struct mem_control_block *)cloc;
        if (loc_mcb->id==id) {

            if (loc_mcb->is_available == 1) {
                errno = EFAULT;
                perror ("this chunk is free");
                return;
            }

            loc_mcb->is_available = 1;




            //remove some fragmentation



            /*prevloc_mcb = (struct mem_control_block *) prevloc;

            //if memory chunk is the first one
            if (cloc == data_t->start) {
                prevloc = cloc;
                prevloc_mcb = (struct mem_control_block *) prevloc;
                cloc = cloc + loc_mcb->size;
                loc_mcb = (struct mem_control_block *)cloc;
                if (loc_mcb->is_available) {
                    prevloc_mcb->size = prevloc_mcb->size + loc_mcb->size;
            }
                goto next;//TODO
            }
            //if memory chunk is the last one
            if ((cloc == data_t->last) && (prevloc_mcb->is_available)) {
                prevloc_mcb->size = prevloc_mcb->size + loc_mcb->size;
                goto next;//TODO
            }
            //if this memory chunk is in the middle
            nextloc = cloc + loc_mcb->size;
            nextloc_mcb = (struct mem_control_block *)nextloc;
            if (nextloc_mcb->is_available)
                loc_mcb->size=loc_mcb->size+nextloc_mcb->size;
            if (prevloc_mcb->is_available)
                prevloc_mcb->size=loc_mcb->size+prevloc_mcb->size;




            next:*/



            if (loc_mcb->is_mapped == 0) {
                printf ("release without munmap\n");
                return;
            }

            //if mapped
            printf ("release with munmap\n");
            munmap(loc_mcb->memloc, (((off_t)(loc_mcb->size) + page_size - 1) /
                                        page_size) * page_size);
            loc_mcb->is_mapped = 0;
            loc_mcb->is_available = 1;
            return;
        }
        prevloc = cloc;
        cloc = cloc + loc_mcb->size;
    }
    errno = EFAULT;
    perror("no chunk with such id");
}


//3: think about logger working with some file, where it contains all information.( i understood, that it can be very helpful, thanks to Anna :) )
//4: but i found your code very interesting( i safe your idea of using mem_block_container, i thought it could be only C++). if all my words help you, i will glad to know it. Good luck! 





//TODO: check poisoning, defragmentation and do more tests!
//TODO: make separate files out of BLOB
int main () {

    //TESTS
    //Do not review them, there is no use in it.


    //INIT:

    /*ma_init(8, 16, "kaban.c");
    ma_deinit();*///--write job



    //ma_init(2000000000, 2000000001, "kaban.c");//ENOMEM

    /*ma_init (8, 16, "kaban.c");
    ma_init (8, 16, "kaban.c");
    ma_deinit();*///EALREADY

    //ma_init(16, 8, "kaban.c");//EBADR

    /*int fd=open("kaban.txt", O_WRONLY|O_CREAT, S_IWUSR);
    close(fd);
    ma_init(8, 16, "kaban.txt");
    ma_deinit();*///EACCESS

    //make test on EFAULT

    //DEINIT:

    //ma_deinit();

    //ALLOC

    /*ma_init (64, 128, "kaban.c");
    int i = ma_alloc(8);
    printf ("%d\n", i);
    ma_deinit();*///--that it actually works

    //int i = ma_alloc(8);--ECANCELED

    /*ma_init (64, 128, "kaban.c");
    int i = ma_alloc(8000);
    ma_deinit();*///E2BIG; note that the size of page is 4096 bytes, not 4!

    //It's too long to check EMLINK, dont forget to do it

    //TODO: Test poisoning

    /*ma_init (10000, 20000, "kaban.c");
    int i = ma_alloc(8000);
    printf ("%d\n", i);
    int j = ma_alloc(8000);
    printf ("%d\n", j);
    ma_deinit();*///ENOMEM

    //FREE

    /*ma_init (10000, 20000, "kaban.c");
    int i = ma_alloc(8000);
    printf ("%d\n", i);
    int k = ma_free(i);
    printf ("result of free: %d\n", k);//should be 1 if program works correctly
    k = ma_free(i);
    printf ("%d\n", k);//should be 0 if program works correctly
    ma_deinit();*///EBUSY

    /*ma_init (10000, 20000, "kaban.c");
    int k = ma_free(2);
    ma_deinit();*///EFAULT

    //int k = ma_free(2);//ECANCELED

    //finish writing ma_free and check more tests

    /*void* buf;
    ma_init (100000, 200000, "kaban.c");
    int i = ma_alloc(4000);
    printf ("%d\n", i);
    int j = ma_alloc(4000);
    printf ("%d\n", j);
    int k = ma_alloc(4000);
    printf ("%d\n", k);
    int l = ma_alloc(4000);
    printf ("%d\n", l);
    ma_free(l);
    ma_deinit();*///EBUSY

    /*void* buf;
    ma_init (100000, 200000, "kaban.c");
    int i = ma_alloc(4000);
    printf ("%d\n", i);
    int j = ma_alloc(4000);
    printf ("%d\n", j);
    int k = ma_alloc(4000);
    printf ("%d\n", k);
    int l = ma_alloc(4000);
    printf ("%d\n", l);
    ma_get(l);
    ma_release(l);
    ma_free(l);
    ma_deinit();*///test on working

    //GET

    //ma_get(1);//ECANCELED

    /*void* buf;
    ma_init (10000, 20000, "kaban.c");
    int i = ma_alloc(8000);
    printf ("%d\n", i);
    buf = ma_get(i);
    ma_deinit();*///test that it atcually works

    //do more tests!!!!!

    //RELEASE+GET

    /*void* buf;
    ma_init (100000, 200000, "kaban.c");
    int i = ma_alloc(4000);
    printf ("%d\n", i);
    int j = ma_alloc(4000);
    printf ("%d\n", j);
    int k = ma_alloc(4000);
    printf ("%d\n", k);
    int l = ma_alloc(4000);
    printf ("%d\n", l);
    ma_release(l);
    buf = ma_get(l);
    ma_deinit();*///EFAULT

    /*void* buf;
    ma_init (100000, 200000, "kaban.c");
    int i = ma_alloc(4000);
    printf ("%d\n", i);
    int j = ma_alloc(4000);
    printf ("%d\n", j);
    int k = ma_alloc(4000);
    printf ("%d\n", k);
    int l = ma_alloc(4000);
    printf ("%d\n", l);
    ma_release(l);
    l = ma_alloc(4000);
    buf = ma_get(l);
    ma_deinit();*///correct!


    return 0;
}
